﻿using System;
using System.Linq;
using System.Collections;
using System.Reflection;
using System.Text;
using LightPersist.Metainfo;

namespace LightPersist.DomainGenerator
{
	public static class ConstructionCodeSerializer
	{
//		class ctx
//		{
//			public readonly List<string> Usings = new List<string>();
//			public readonly List<string> References = new List<string>();
//		}
//
//		public static Assembly GetConstructionAssembly<T>(T data)
//		{
//			throw new NotImplementedException();
//			var compiler = new CSharpCodeProvider(new Dictionary<string, string> {{"CompilerVersion", "v3.5"}});
//			var options = new CompilerParameters {GenerateInMemory = true};
//
//
//			var code = @"
//public static class Construct
//{
//	public static {0} Load()
//	{
//		
//	}
//}
//".Arg(typeof(T).Name);
//			var result = compiler.CompileAssemblyFromSource(options, code);
//			if (result.Errors.Count > 0)
//			{
//				throw new Exception(result.Errors[0].ToString());
//			}
//			return result.CompiledAssembly;
//		}

		public static string GetConstuctionCode(object data)
		{
			string code;
			var res = GetConstuctionCode(data, out code);
			return code + "return " + res + ";";
		}

		private static int _varNumber;

		static string GetConstuctionCode(object data, out string additionalCode)
		{
			if (data == null)
			{
				additionalCode = null;
				return "null";
			}
			if (data is string)
			{
				additionalCode = null;
				return "@\"" + data + "\"";
			}
			var type = data.GetType();
			if (type.IsPrimitive)
			{
				additionalCode = null;
				return data.ToString().ToLower();
			}
			if (type.IsEnum)
			{
				additionalCode = null;
				return type.FullName + "." + data;
			}
			var varName = "v" + ++_varNumber;
			var sb = new StringBuilder();
			var surrogate = Activator.CreateInstance(type);
			sb.AppendLine("var " + varName + " = new " + type.FullName + "();");
			foreach (var prop in
				from x in type.GetProperties()
				let isList = IsList(x.PropertyType)
				orderby isList
				select new { x, isList }
			)
			{
				// Trace.WriteLine("\tType:\t" + type + "\tProperty:\t" + prop.x.Name);
				var propertyInfo = prop.x;
				if (prop.isList)
				{
					var value = propertyInfo.GetValue(data, null);
					foreach (var item in (IEnumerable) value)
					{
						string subCode;
						var subRes = GetConstuctionCode(item, out subCode);
						sb.AppendLine(subCode + varName + "." + propertyInfo.Name + ".Add(" + subRes + ");");
					}
				}
				else if (prop.x.GetAccessors().Length == 2 && prop.x.GetAccessors().All(x => x.IsPublic))
				{
					var attrs = prop.x.GetCustomAttributes(typeof (CodeInitializerAttribute), true).OfType<CodeInitializerAttribute>().ToArray();
					if (attrs.Length > 0)
					{
						var attr = attrs[0];
						var argsValue = new string[attr.Args.Length];
						for (var i = 0; i < attr.Args.Length; i++)
						{
							var pi = type.GetProperty(attr.Args[i],
															  BindingFlags.Public | BindingFlags.NonPublic |
															  BindingFlags.Instance | BindingFlags.Static);
							if (pi.PropertyType != typeof(string))
							{
								throw new ApplicationException(
									"CodeInitializerAttribute for type '{0}' property '{1}' has argument property '{2}' that should be string.");
							}
							argsValue[i] = (string)pi.GetValue(data, null);
						}
						var initializer = string.Format(attr.CodePattern, argsValue);
						sb.AppendLine(varName + "." + propertyInfo.Name + " = " + initializer + ";");
					}
					else
					{
						var value = propertyInfo.GetValue(data, null);
						if (!Equals(propertyInfo.GetValue(surrogate, null), value))
						{
							string subCode;
							var subRes = GetConstuctionCode(value, out subCode);
							sb.AppendLine(subCode + varName + "." + propertyInfo.Name + " = " + subRes + ";");
						}
					}
				}
			}

			additionalCode = sb.ToString();
			return varName;
		}

		private static bool IsList(Type type)
		{
			// duck typing
			return type.GetMethod("Add") != null && typeof (IEnumerable).IsAssignableFrom(type);
		}
	}
}
